"""
The Results Policy handles everything that happens from everything after a spell has been [activated] and the targets
picked. It controls how the spell is pushed into the mission queue (if at all), and what happens when the spell's
mission is started and / or continued. Most of what you want a spell to actually "do" will be coded and handled by the
Results policy.
"""

import GerikSpellsUtils
from GSTargetInfoPolicies import GSNoTarget
from SdToolkitAdvanced import *

import copy # For making copies of the target policy

class GSBase:
    """
    This is the base policy class for the Results policy. It's useful for making your own policy classes, as
    it has the necessary functions that should be overridden (or at least ignored with a very good idea
    of why you're ignoring them).
    """
    def __init__(self):
        """
        Arguments:
            @None

        Returns:
            > None

        Description:
        <FuncImplemented>
        """
        pass
    
    def castSpell(self, pTargetInfo):
        """
        Arguments:
            @pTargetInfo: The TargetInfo class.

        Returns:

        Description:

        <FuncNotImplemented>
        """
        raise NotImplementedError

    def startSpellMission(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The group who is casting the spell.
            @iData1: The iData1 variable
            @iData2: The iData2 variable
            @iData3: The iData3 variable

        Returns:
            >True to have the mission continue for multiple turns.
            >False to end the mission at the end of this function.

        Description:
        <FuncNotImplemented>
        """
        raise NotImplementedError

    def startSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The group who is casting the spell.
            @pTargetInfo: The TargetInfoPolicy class used to describe the target.

        Returns:
            >True to have the mission continue for multiple turns.
            >False to have the mission stop as the end of this function.

        Description:

        <FuncNotImplemented>
        """
        raise NotImplementedError

    def startSpellResultsOnTarget(self, pCastingGroup, pTarget):
        """
        Arguments:
            @pCastinGroup: The group casting the spell.
            @pTarget: The specific target object that is one of the affected targets of the spell.

        Returns:
            >True to have the mission continue for multiple turns.
            >False to have the mission stop at the end of this function.

        Description:

        <FuncNotImplemented>
        """
        raise NotImplementedError

    def continueSpellMission(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The group casting the spell.
            @iData1: The iData1 variable.
            @iData2: The iData2 variable.
            @iData3: The iData3 variable.

        Returns:
            >True if the spell's mission should continue after this function.
            >False if the spell's mission should finish after this function.

        Description:
        <FuncNotImplemented>
        """
        raise NotImplementedError

    def continueSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The group casting the spell.
            @pTargetInfo: The TargetInfoPolicy used to describe the target.

        Returns:
            >True if the spell's mission should continue after this function.
            >False if the spell's mission should finish after this function.

        Description:
        <FuncNotImplemented>
        """
        raise NotImplementedError

    def continueSpellResultsOnTarget(self, pCastingGroup, pTarget):
        """
        Arguments:
            @pCastingGroup: The group casting the spell.
            @pTarget: A target object that is being affected by the spell.

        Returns:
            >True if the spell's mission should continue after this function.
            >False if the spell's mission should finish after this function.

        Description:

        <FuncNotImplemented>
        """
        raise NotImplementedError

    def cleanupSpell(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: THe group casting the spell.
            @iData1: The iData1 variable.
            @iData2: The iData2 variable.
            @iData3: The iData3 variable.

        Returns:
            >None

        Description:
            >This function is called whenever a spell mission is removed from the queue. Use it to clean up any
            variables that need cleaning, such as if you put script data on any objects.
        <FuncImplemented>
        """
        return

class GSMission(GSBase):
    """
    This policy will make it so that all the steps that happen to actually push a mission are covered for
    you, and all you need to do is override continueSpellResultsOnTarget to do the spell results on the specified
    target.
    """
    def __init__(self, pTargetInfoPolicy):
        """
        Arguments:
            @pTargetInfoPolicy: The TargetInfo policy class for what will be the target of the spell.

        Returns:
            >None

        Description:
            >None
        <FuncImplemented>
        """
        
        GSBase.__init__(self)
        self.m_pTargetInfoPolicy = pTargetInfoPolicy

    def getTargetInfoPolicy(self):
        """
        Arguments:
            @None

        Returns:
            >Returns the target info policy that is being used for this spell.

        Description:
            >None
        """
        return self.m_pTargetInfoPolicy

    def setTargetInfoPolicy(self, pTargetInfoPolicy):
        """
        Arguments:
            @pTargetInfoPolicy: The new target info policy.

        Returns:
            >None

        Description:
            >This sets the target info policy for the spell.
        """
        self.m_pTargetInfoPolicy = pTargetInfoPolicy

    def castSpell(self, pTargetInfo):
        """
        Arguments:
            @pTargetInfo: The TargetInfo class selected as the final target during targetting.
    
        Returns:
            @None

        Description:
            >Pushes the spell mission for the group.
        <FuncImplemented>
        """
        self.pushSpellMission(pTargetInfo.getData1(), pTargetInfo.getData2(), pTargetInfo.getData3())

    def startSpellMission(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The group casting the spell.
            @iData1: The iData1 variable for this spell mission.
            @iData2: The iData2 variable for this spell mission.
            @iData3: The iData3 variable for this spell mission.

        Returns:
            >True if the the group should continue to cast the spell.
            >False if the group is done casting.

        Description:
            >Automatically converts the iData__ variables into a TargetInfo policy class,
            then passes the target info onto startSpellResults for handling.
        <FuncImplemented>
        """
        pTargetInfo = copy.copy(self.getTargetInfoPolicy())
        pTargetInfo.setTargetWithData(iData1, iData2, iData3)
        return self.startSpellResults(pCastingGroup, pTargetInfo)

    def startSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup:  The group casting the spell.
            @pTargetInfo: The TargetInfo policy class that holds the target.
        
        Returns:
            >True if the group should continue casting.
            >False if the group should stop casting.

        Description:
            >This will call startSpellResultsOnTarget for each of the targets that should be affected.
        <FuncImplemented>
        """
        pListTargets = pTargetInfo.getAffectedTargetList(pCastingGroup, pCastingGroup.plot())
        bContinue = True
        for pTarget in pListTargets:
            if not self.startSpellResultsOnTarget(pCastingGroup, pTarget):
                bContinue = False

        return bContinue

    def continueSpellMission(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The group casting the spell.
            @iData1: The iData1 variable for this spell mission.
            @iData2: The iData2 variable for this spell mission.
            @iData3: The iData3 variable for this spell mission.

        Returns:
            >True if the the group should continue to cast the spell.
            >False if the group is done casting.

        Description:
            >Automatically converts the iData* variables into a TargetInfo class,
            then passes the target info onto continueSpellResults for handling.
        <FuncImplemented>
        """
        pTargetInfo = copy.copy(self.getTargetInfoPolicy())
        pTargetInfo.setTargetWithData(iData1, iData2, iData3)
        return self.continueSpellResults(pCastingGroup, pTargetInfo)

    def continueSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup:  The group casting the spell.
            @pTargetInfo: The TargetInfo policy class that holds the target.
        
        Returns:
            >True if the group should continue casting.
            >False if the group should stop casting.

        Description:
            >This will call continueSpellResultsOnTarget for each of the targets that should be affected.
        <FuncImplemented>
        """
        pListTargets = pTargetInfo.getAffectedTargetList(pCastingGroup, pCastingGroup.plot())
        bContinue = True
        for pTarget in pListTargets:
            if not self.continueSpellResultsOnTarget(pCastingGroup, pTarget):
                bContinue = False

        return bContinue

class GSMissionNoTarget(GSMission):
    """
    This policy will make it so that all the steps that happen to actually push a spell mission are covered for
    you, and all you need to do is override the startSpellResults and, if necessary, the continueSpellResults 
    function. This is useful for spells that act as "immediates" that don't use a target system.
    """
    def __init__(self):
        GSMission.__init__(self, GSNoTarget())

class GSMissionCasterCounter(GSMission):
    """
    This policy will put a counter on each unit in the group, and every turn each unit will end it's turn and increment
    it's counter. When a unit's counter reaches a certain amount, then the continueSpellResultsOnTarget function is called. Use
    this so that a unit would have to wait a predefined amount of turns before being able to launch it's spell's
    functionality.
    """
    def __init__(self, pTargetInfoPolicy):
        """
        Arguments:
            @pTargetInfoPolicy: The TargetInfo policy class that is used as the end target of the spell.

        Returns:
            >None

        Description:
            >None
        <FuncImplemented>
        """
        GSMission.__init__(self, pTargetInfoPolicy)
        self.m_iGoal = 0
        self.m_iIncrementValue = 1

    def getCounterGoal(self, pUnit):
        """
        Arguments:
            @pUnit: The unit whose counter goal we're checking.

        Returns:
            >The number that the counter must reach before calling continueSpellResults. 

        Description:
            >This defaults to the iGoal value set using setCounterGoal, but this function can be overridden to 
            make the goal unit-specific.
            
        """
        return self.m_iGoal

    def setCounterGoal(self, iGoal):
        """
        Arguments:
            @iGoal: The number that the counter must reach before calling continueSpellResults.

        Returns:
            >None

        Description:
            >None
        """
        self.m_iGoal = iGoal

    def getIncrementValue(self):
        """
        Arguments:
            @None

        Returns:
            >The number that should be added to the casting unit's counter every turn.

        Description:
            >None
        """
        return self.m_iIncrementValue

    def setIncrementValue(self, iValue):
        """
        Arguments:
            @iValue: The number that should be added to the casting unit's counter eveyr turn.

        Returns:
            >None

        Description:
            >None
        """
        self.m_iIncrementValue = iValue

    def getCompletionValue(self, pUnit):
        """
        Arguments:
            @pUnit: The unit whose completion value we're trying to find out.

        Returns:
            >The completion value of the spell for a specified units.
            >-1 if the unit is not currently in the process of continued casting.
        Description:
            >None
        """
        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()
        if (sdObjectValExists("GerikSpells", pUnit, szScriptDataKeyTotalValue)):
            return sdObjectGetVal("GerikSpells", pUnit, szScriptDataKeyTotalValue)
        else:
            return -1

    def getScriptDataKeyTotalValue(self):
        """
        Arguments:
            @None

        Returns:
            >The string that is used as a key when getting the data from the unit's script data using sdToolkitAdvanced.

        Description:
            >None
        """

        return "%d" % self.getSpellNumber()

    def startSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The casting group.
            @pTargetInfo: The target info, if any.

        Returns:
            >True, to ensure that the spell gets continued.

        Description:
            >None
        <FuncImplemented>
        """
        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()
        for pUnit in pCastingGroup.getUnits():
            if self.canUnitCast(pUnit, pUnit.plot(), False):
                if not sdObjectValExists("GerikSpells", pUnit, szScriptDataKeyTotalValue):
                    sdObjectSetVal("GerikSpells", pUnit, szScriptDataKeyTotalValue, 0)

        return True

    def continueSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The casting group.
            @pTargetInfo: The TargetInfo class, if any.

        Returns:
            >True if any of the units still need to cast.
            False if all units are done casting.

        Description:
            >None
        <FuncImplemented>
        """
        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()

        bContinue = False
        for pUnit in pCastingGroup.getUnits():
            if not pUnit.canMove():
                bContinue = True
            elif sdObjectValExists("GerikSpells", pUnit, szScriptDataKeyTotalValue):
                iValue = sdObjectGetVal("GerikSpells", pUnit, szScriptDataKeyTotalValue)
                iValue += self.getIncrementValue()
                if iValue >= self.getCounterGoal(pUnit):
                    if self.continueSpellResultsOnTarget(pUnit, pTargetInfo):
                        bContinue = True
                        sdObjectSetVal("GerikSpells", pUnit, szScriptDataKeyTotalValue, 0)
                    else:
                        sdObjectValWipe("GerikSpells", pUnit, szScriptDataKeyTotalValue)
                else:
                    bContinue = True
                    sdObjectSetVal("GerikSpells", pUnit, szScriptDataKeyTotalValue, iValue)
                    pUnit.finishMoves()

        return bContinue

    def continueSpellResultsOnTarget(self, pCastingUnit, pTargetInfo):
        """
        Arguments:
            @pCastingUnit: The casting unit whose counter just completed counting.
            @pTargetInfo: The TargetInfo class, if any.

        Returns:
            >None

        Description:
            This function should be overridden to include the details of what happens once a unit has completed it's
            counting.
        <FuncNotImplemented>
        """

        raise NotImplementedError

    def cleanupSpell(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The casting group that finished / stopped casting the spell.
            @iData1, iData2, iData3: The iData variables.

        Returns:
            >None

        Description:
            >This function used to reset all the units in the stack to their default, non-existant counter. If
            overridden, make sure to call this function!
        <FuncImplemented>
        """
        szScriptDataKey = self.getScriptDataKeyTotalValue()
        for pUnit in pCastingGroup.getUnits():
            if sdObjectValExists("GerikSpells", pUnit, self.getScriptDataKeyTotalValue()):
                sdObjectValWipe("GerikSpells", pUnit, self.getScriptDataKeyTotalValue())
                
    

class GSMissionTargetCounter(GSMission):
    """
    This policy will put a counter on the target of a spell, and every turn each unit in the group that can cast on
    the target will increment the counter on the target. When the target's counter reaches a certain amount, then 
    continueSpellResultsOnTarget will be called for the results on the target. All units casting towards that target
    will then stop casting the spell.
    """
    def __init__(self, pTargetInfo):
        GSMission.__init__(self, pTargetInfo)
        self.m_iGoal = 0
        self.m_iIncrementValue = 1

    def getCounterGoal(self, pTarget):
        """
        Arguments:
            @pTarget: The target whose counter's goal we're getting.
        
        Returns:
            >The number that the counter on the target must reach before calling continueSpellResults.

        Description:
            >This will by default just return the value of iGoal specified in setCounterGoal, but can be overridden
            to allow for target-specific goal amounts.
        """
        return self.m_iGoal

    def setCounterGoal(self, iGoal):
        """
        Arguments:
            @iGoal: The number that the counter on the target must reach before calling continueSpellResults.

        Returns:
            >None

        Description:
            >None
        """
        self.m_iGoal = iGoal

    def getIncrementValue(self, pUnit):
        """
        Arguments:
            @pUnit: The unit that is adding to the counter.

        Returns:
            >None

        Description:
            >Override this function to have different units increment at different rates. Otherwise, all
            units will increment the counter at the same rate, defined in setIncrementValue.
        """
        return self.m_iIncrementValue

    def setIncrementValue(self, iIncrementValue):
        """
        Arguments:
            @iIncrementValue: The base value that each unit should increment the target counter every turn.

        Returns:
            >None

        Description:
            >Override the getIncrementValue function to make units have individually determined inrcrement values.
        """

    def getCompletionValue(self, pTarget):
        """
        Arguments:
            @pTarget: The target being affected by the spell.

        Returns:
            >The total value of completion thus far on the target.
            >-1 if the target is not currently being targetted by the spell.

        Description:
            >None
        """
        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()

        if (not sdObjectValExists("GerikSpells", pTarget, szScriptDataKeyTotalValue)):
            return -1

        return sdObjectGetVal("GerikSpells", pTarget, szScriptDataKeyTotalValue)

    def getCompletionTurnsLeft(self, pTarget):
        """
        Arguments:
            @pTarget: The target object to check for how many turns left it will be before it's counter hits the goal.

        Returns:
            >None

        Description:
            >This checks for all the units currently casting the spell on the target, and calculates how many turns it
            will take them to finish.
        """
        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()
        szScriptDataKeyCasterList = self.getScriptDataKeyCasterList()

        if (not sdObjectValExists("GerikSpells", pTarget, szScriptDataKeyTotalValue)):
            return -1

        iIncrementValuePerTurn = 0
        pCasterList = sdObjectGetVal("GerikSpells", pTarget, szScriptDataKeyCasterList)
        for pCasterID in pCasterList:
            pUnit = gc.getPlayer(pCasterID[0]).getUnit(pCasterID[1])
            iIncrementValuePerTurn += self.getIncrementValue(pUnit)

        iIncrementsLeft = self.getCounterGoal(pTarget) - sdObjectGetVal("GerikSpells", pTarget, szScriptDataKeyTotalValue)
        iTurnsLeft = iIncrementsLeft / iIncrementValuePerTurn

        if (iIncrementsLeft % iIncrementValuePerTurn != 0):
            iTurnsLeft += 1

        return iTurnsLeft

    def getUnitsTargetInfo(self, pUnit):
        """
        Arguments:
            @pUnit: The unit that is currently targetting a target with a spell.

        Returns:
            >The TargetInfo class for the units target.
            >None if this unit has no target for this spell.

        Description:
            >None
        """
        szScriptDataKeyCastersTarget = self.getScriptDataKeyCastersTarget()
        if (not sdObjectValExists("GerikSpells", pUnit, szScriptDataKeyCastersTarget)):
            return None
            
        tDataTuple = sdObjectGetVal("GerikSpells", pUnit, szScriptDataKeyCastersTarget)
        pTargetInfo = copy.copy(self.getTargetInfoPolicy())
        pTargetInfo.setTargetWithData(tDataTuple[0], tDataTuple[1], tDataTuple[2])
        return pTargetInfo

    def getScriptDataKeyTotalValue(self):
        """
        Arguments:
            @None

        Returns:
            >The string that is used as a key when getting the data from the target's script data using
            sdToolkitAdvanced. This key will get the total value of the target's counter thus far.

        Description:
            >None
        """

        return "%dTotal" % self.getSpellNumber()

    def getScriptDataKeyCasterList(self):
        """
        Arguments:
            @None

        Returns:
            >The string that is used as a key when getting the data from the target's script data using
            sdToolkitAdvanced.

        Description:
            >This key will get the list of unit owner/id tuples of every unit that is targetting a target.
        """
        return "%dCasters" % self.getSpellNumber()

    def getScriptDataKeyCastersTarget(self):
        """
        Arguments:
            @None

        Returns:
            The string that is used as a key when getting the data from the caster's script data using
            sdToolkitAdvanced. 

        Description:
            >This key will get the iData1/iData2/iData3 tuple of the target class that is being targetted by the unit.
        """
        return "%dTarget" % self.getSpellNumber()

    def startSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The casting group that has recently started affecting this target with the spell.
            @pTargetInfo: The target info class that is the target for the spell.
        
        Returns:
            >True, to ensure that the spell gets continued.

        Description:
            >None
        <FuncImplemented>
        """

        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()
        szScriptDataKeyCasterList = self.getScriptDataKeyCasterList()
        szScriptDataKeyCastersTarget = self.getScriptDataKeyCastersTarget()

        pTarget = pTargetInfo.getTarget()
        pTargetInfoDataTuple = ( pTargetInfo.getData1(), pTargetInfo.getData2(), pTargetInfo.getData3() )

        # Make the list of casters and add the target to the unit's target script.
        # This is used for determining if the target has been finished by someone else. If the spell
        # is the type where the mission doesn't repeat, than the value stored in the unit's scipt data
        # for "CastersTarget" is deleted, so that the unit knows next time around that their target
        # was finished.
        pNewCasterList = []
        for pUnit in pCastingGroup.getUnits():
            if self.canUnitCast(pUnit, pUnit.plot(), False):
                sdObjectSetVal("GerikSpells", pUnit, szScriptDataKeyCastersTarget, pTargetInfoDataTuple)
                pNewCasterList.append( (pUnit.getOwner(), pUnit.getID() ) )

        if not sdObjectValExists("GerikSpells", pTarget, szScriptDataKeyTotalValue):
            # If this is a target that is "fresh" (hasn't been targetted yet), setup
            # it's total value completed to 0.
            sdObjectSetVal("GerikSpells", pTarget, szScriptDataKeyTotalValue, 0)

            # Also, simply set it's caster list to this groups casting list.
            sdObjectSetVal("GerikSpells", pTarget, szScriptDataKeyCasterList, pNewCasterList)
        else:
            # This target already has units targetting it, so just add these casters on.
            pCurrentCasterList = sdObjectGetVal("GerikSpells", pTarget, szScriptDataKeyCasterList)
            pCurrentCasterList.extend(pNewCasterList)
            sdObjectSetVal("GerikSpells", pTarget, szScriptDataKeyCasterList, pCurrentCasterList)


        return True

    def continueSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The group that is continuing the casting.
            @pTargetInfo: The target info class.

        Returns:
            >True if the target's counter is still not done, to ensure that the group continues casting.
            >False if the target's counter is reached it's goal, meaning the group can stop casting.

        Description:
            >None
        <FuncImplemented>
        """
        szScriptDataKeyTotalValue = self.getScriptDataKeyTotalValue()
        szScriptDataKeyCasterList = self.getScriptDataKeyCasterList()
        szScriptDataKeyCastersTarget = self.getScriptDataKeyCastersTarget()
        pTarget = pTargetInfo.getTarget()

        bContinue = False
        for pUnit in pCastingGroup.getUnits():
            # Don't do anything if this unit can't move.
            if not pUnit.canMove():
                return True

            # Add this unit's increment to the counter.
            elif sdObjectValExists("GerikSpells", pTarget, szScriptDataKeyTotalValue):
                iValue = sdObjectGetVal("GerikSpells", pTarget, szScriptDataKeyTotalValue)
                iValue += self.getIncrementValue(pUnit)
                pUnit.finishMoves()

                # This unit "finished" the counter. Tell the spell class.
                if iValue >= self.getCounterGoal(pTarget):
                    bContinue = self.continueSpellResultsOnTarget(pUnit, pTargetInfo)

                    # If this is a continue, reset the value so that we can continue.
                    if bContinue:
                        sdObjectSetVal("GerikSpells", pTarget, szScriptDataKeyTotalValue, 0)
                    
                    # If this is NOT a continue, then we need to tell all of the units casting on the target
                    # that we finished it. Luckily, we've all that information stored. Also, we'll wipe the
                    # counter variable clean.
                    else:
                        for iOwner, iID in sdObjectGetVal("GerikSpells", pTarget, szScriptDataKeyCasterList):
                            pUnit = gc.getPlayer(iOwner).getUnit(iID)
                            sdObjectValWipe("GerikSpells", pUnit, szScriptDataKeyCastersTarget)

                        sdObjectValWipe("GerikSpells", pTarget, szScriptDataKeyTotalValue)
                        sdObjectValWipe("GerikSpells", pTarget, szScriptDataKeyCasterList)
                else:
                    bContinue = True
                    sdObjectSetVal("GerikSpells", pTarget, szScriptDataKeyTotalValue, iValue)


        return bContinue

    def continueSpellResultsOnTarget(self, pCastingUnit, pTargetInfo):
        """
        Arguments:
            @pCastingUnit: The casting unit who just added the last required amount to the target counter.
            @pTargetInfo: The TargetInfo class.

        Returns:
            >None

        Description:
            >This function should be overridden to include the details of what happens to tha target once the target's
            counter hits the goal.
        <FuncNotImplemented>
        """

        raise NotImplementedError

    def cleanupSpell(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The casting group that finished / stopped casting the spell.
            @iData1, iData2, iData3: The iData variables.

        Returns:
            >None

        Description:
            >This function tells the target that the units in this group are no longer targetting them.
        <FuncImplemented>
        """
        szScriptDataKeyCastersTarget = self.getScriptDataKeyCastersTarget()
        szScriptDataKeyCasterList = self.getScriptDataKeyCasterList()

        pTargetInfo = copy.copy(self.getTargetInfoPolicy())

        # Now get the caster list from the target, and remove all the units that need removing.
        for pUnit in pCastingGroup.getUnits():
            pUnitData = ( pUnit.getOwner(), pUnit.getID() )

            if sdObjectValExists("GerikSpells", pUnit, szScriptDataKeyCastersTarget):
                tTargetData = sdObjectGetVal("GerikSpells", pUnit, szScriptDataKeyCastersTarget)
                pTargetInfo.setTargetWithData(tTargetData[0], tTargetData[1], tTargetData[2])

                # Remove this unit from the target's "caster list" (list of units targetting it).
                pCasterList = sdObjectGetVal("GerikSpells", pTargetInfo.getTarget(), szScriptDataKeyCasterList)

                while pCasterList.count(pUnitData) > 0:
                    pCasterList.remove(pUnitData)

                # Replace the casters list with the new, updated version without this unit.
                sdObjectSetVal("GerikSpells", pTargetInfo.getTarget(), szScriptDataKeyCasterList, pCasterList)

                # Remove this target from the caster's script data.
                sdObjectValWipe("GerikSpells", pUnit, szScriptDataKeyCastersTarget)



class GSBuild(GSBase):
    """
    GSBuild Policy
    This policy means that the spell functions as a worker's build policy, in which the unit will work (according to
    it's work rate) towards a specific goal. When that goal is reached, then the continueSpellResults function is called.
    Typically, this function would then do something to the terrain (such as plant a forest).
    """
    def __init__(self):
        """
        Arguments:
            @None

        Returns:
            >None

        Description:
            >None

        <FuncImplemented>
        """
        GSBase.__init__(self)
        self.m_iBuildTime = 0
        self.m_bTeamSpecific = False

    def getBuildTime(self):
        """
        Arguments:
            @None

        Returns:
            >Base build time needed for workers to complete work on a plot

        Description:
            >None
        """
        return self.m_iBuildTime

    def setBuildTime(self, iBuildTime):
        """
        Arguments:
            @iBuildTime: The base build time that will be needed for workers to complete work on a plot.

        Returns:
            >None

        Description:
            >None
        """
        self.m_iBuildTime = iBuildTime

    def isTeamSpecific(self):
        """
        Arguments:
            @None

        Returns:
            >Whether or not the spell is considered "team specific".

        Description:
            >If true, then a unit will only progress on a build for his/her team.
            >If false, a unit's progression is coupled with other's progress.
        """
        return self.m_bTeamSpecific

    def setTeamSpecific(self, bSpecific):
        """
        Arguments:
            @bSpecific: Whether or not the spell should be considered "team specific".

        Returns:
            >None

        Description:
            >Sets whether or not the spell is considered "team specific".
            >If true, then a unit will only progress on a build for his/her team.
            >If false, a unit's progression is coupled with other's progress.
        """
        self.m_bTeamSpecific = bSpecific

    def getScriptDataKeyTotalValue(self, eTeam=-1):
        """
        Arguments:
            @iTeam: The team number of the group creating the build.
    
        Returns:
            >The string that will be used on the target's object as the script data key.

        Description:
            >None
        """
        if (self.isTeamSpecific()):
            return "%d-%d" % (self.getSpellNumber(), iTeam)
        else:
            return "%d--1" % (self.getSpellNumber())
            


    def castSpell(self, pTargetInfo):
        """
        Arguments:
            @pTargetInfo: The TargetInfo class selected as the final target during targetting.
    
        Returns:
            >None

        Description:
            >Pushes the spell mission for the group.
        <FuncImplemented>
        """
        self.pushSpellMission(-1, -1, -1)
        
    def startSpellMission(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The group that is currently running this mission.
            @iData1, iData2, iData3: The iData variables of the mission.

        Returns:
            >True or False, depending on if the mission should continue.

        Description:
            >None
        <FuncImplemented>
        """
        # See if work has not yet started on this. If it hasn't, make the variable.
        szScriptDataKey = self.getScriptDataKeyTotalValue(pCastingGroup.getTeam())
        if (not sdObjectValExists("GerikSpells", pCastingGroup.plot(), szScriptDataKey)):
            sdObjectSetVal("GerikSpells", pCastingGroup.plot(), szScriptDataKey, 0)

        return True

    def startSpellResults(self, pCastingGroup, pTargetInfo):
        """
        This is not used in this policy.
        <FuncNotNeeded>
        """
        raise NotImplementedError

    def startSpellResultsOnTarget(self, pCastingGroup, pTarget):
        """
        This is not used in this policy.
        <FuncNotNeeded>
        """
            
    def continueSpellMission(self, pCastingGroup, iData1, iData2, iData3):
        """
        Arguments:
            @pCastingGroup: The group that is currently running this mission.
            @iData1, iData2, iData3: The iData variables of the mission.

        Returns:
            >True if the group must continue building.
            >False if the group should stop working (either the build is complete or it can no longer be built).

        Description:
            >When run, this adds the amount of work that the group of workers can do to the total of the plot. The
            total of the plot can be found by finding the script data with mod "GerikSpells" and script data as
            "spellnum-team", where "spellnum" is the number of the spell, and "team" is the number of the team (-1
            for a non-team specific build).
        <FuncImplemented>
        """
        szScriptDataKey = self.getScriptDataKeyTotalValue(pCastingGroup.getTeam())
        
        bContinue = True
        if (not sdObjectValExists("GerikSpells", pCastingGroup.plot(), szScriptDataKey)):
            # The build must have been finished by another group. We'll just return false to end our mission.
            return False

        iCurrentCompletionValue = sdObjectGetVal("GerikSpells", pCastingGroup.plot(), szScriptDataKey)
        for pUnit in pCastingGroup.getUnits():
            if (pUnit.canMove()):
                if (bContinue):
                    # Checks that we can continue building
                    if (not self.canGroupCast(pCastingGroup, pCastingGroup.plot(), False)):
                        bContinue = False
                    else:
                        iCurrentCompletionValue += pUnit.workRate(False)
                        pUnit.finishMoves()
                
                        # Finally, call the continueSpellResults to actually find out what we're building.
                        # Also remove the script data key, so other groups know that we've finished this build.
                        if (iCurrentCompletionValue >= self.getBuildTime()):
                            bContinue = False
                            self.continueSpellResults(pCastingGroup, GSNoTarget())
                            sdObjectValWipe("GerikSpells", pCastingGroup.plot(), szScriptDataKey)

        if bContinue:
            sdObjectSetVal("GerikSpells", pCastingGroup.plot(), szScriptDataKey, iCurrentCompletionValue)

        return bContinue

    def continueSpellResults(self, pCastingGroup, pTargetInfo):
        """
        Arguments:
            @pCastingGroup: The group that finished the build.
            @pTargetInfo: Will be GSNoTarget() for this policy.

        Returns:
            >None

        Description:
            >This is called as soon as the build finishes. This must be overridden with the actions
            that happen once the build has indeed finished.
        <FuncNotImplemented>
        """
        raise NotImplementedError

    def continueSpellResultsOnTarget(self, pCastingGroup, pTargetInfo):
        """
        Not used in this policy
        <FuncNotNeeded>
        """

    def getBuildTurnsLeft(self, pPlot, iTeam=-1):
        """
        Arguments:
            @pPlot: The plot that is being queried for how many turns are left for this spell to complete with the
                   current workforce.
            @iTeam: The number of the team. Use -1 for a build where all teams contribute to the same goal.

        Returns:
            >How many turns are left to build.

        Description:
            >None
        """
        szScriptDataKey = "%d-%d" % (self.getSpellNumber(), iTeam)
        iBuildTurnsLeft = GerikSpellsUtils.getBuildTurnsLeft(
            pPlot, self.getSpellNumber(), self.getBuildTime(),  szScriptDataKey)
        return iBuildTurnsLeft

    def getBuildTurnsLeftWithHelp(self, pPlot, pGroup, iTeam=-1):
        """
        Arguments:
            @pPlot: The plot that is being queried for how many turns are left for this spell to complete with the
                   current workforce.
            @pGroup: The group that this calculation pretends is working on the project.
            @iTeam: The number of the team. Use -1 for a build where all teams contribute to the same goal.

        Returns:
            >How many turns are left to finish building if the group pGroup were to join building right now.
        
        Description:
            >This is useful for showing how many turns would be left when editting the spell's help text. Make pGroup
            the selected group when the user is hovering over the spell's action button.

            >Note: This will not take into account if the group is already working on the plot! The same bug occurs in
            Civ4 v1.61! I have written the function so that it doesn't correct this, as this way the data is the
            same as Civ4.
        """
        szScriptDataKey = "%d-%d" % (self.getSpellNumber(), iTeam)
        iBuildTurnsLeft = GerikSpellsUtils.getBuildTurnsLeftWithHelp(
            pPlot, pGroup, self.getSpellNumber(), self.getBuildTime(), szScriptDataKey
            )
        return iBuildTurnsLeft
